package com.imooc.pan.server.common.aspect;

import com.imooc.pan.cache.core.constants.CacheConstants;
import com.imooc.pan.core.response.R;
import com.imooc.pan.core.response.ResponseCode;
import com.imooc.pan.core.utils.JwtUtil;
import com.imooc.pan.server.common.annotation.LoginIgnore;
import com.imooc.pan.server.common.utils.UserIdUtil;
import com.imooc.pan.server.modules.user.constants.UserConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;

/**
 * 统一的登录校验逻辑切面实现类
 * @author hhb
 * @data 2024/6/7 21:06
 */
@Component
@Aspect
@Slf4j
public class CommonLoginAspect {

    /**
     * 登录认证参数名称
     */
    private static final String LOGIN_AUTH_PARAM_NAME = "authorization";

    /**
     * 请求头登录认证key
     */
    private static final String LOGIN_AUTH_REQUEST_HEAD_NAME = "Authorization";

    /**
     * 切点表达式
     */
    private final static String POINT_CUT = "execution(* com.imooc.pan.server.modules.*.controller..*(..))";

    @Autowired
    private CacheManager cacheManager;

    @Pointcut(value = POINT_CUT)
    public void loginAuth(){

    }

    /**
     * 校验逻辑：
     * 判断方法是否需要拦截
     * 需要拦截
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("loginAuth()")
    public Object loginAuthAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        if(checkNeedCheckLoginInfo(proceedingJoinPoint)){
            //需要拦截
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = requestAttributes.getRequest();
            String requestURI = request.getRequestURI();
            log.info("成功拦截到请求，URI为：{}",requestURI);
            if(!saveCheckUserId(request)){
                log.warn("成功拦截到请求，URI为：{}，检测到用户未登录，跳转到登录页面",requestURI);
                return R.fail(ResponseCode.NEED_LOGIN);
            }
            log.info("成功拦截到请求，URI为：{},请求通过",requestURI);
        }
        return proceedingJoinPoint.proceed();
    }

    /**
     * 判断accessToken是否存在并保存用户Id
     * @param request
     * @return
     */
    private boolean saveCheckUserId(HttpServletRequest request) {
        String accessToken = request.getHeader(LOGIN_AUTH_REQUEST_HEAD_NAME);
        if(StringUtils.isBlank(accessToken)){
            accessToken = request.getParameter(LOGIN_AUTH_PARAM_NAME);
        }
        if(StringUtils.isBlank(accessToken)){
            return false;
        }
        //解析accessToken获取用户id
        Object userId = JwtUtil.analyzeToken(accessToken, UserConstants.LOGIN_USER_ID);
        if(Objects.isNull(userId)){
            return false;
        }
        //从缓存中获取accessToken
        Cache cache = cacheManager.getCache(CacheConstants.R_PAN_CACHE_NAME);
        String accessTokenRedis = cache.get(UserConstants.USER_LOGIN_PREFIX+userId,String.class);
        if(Objects.isNull(accessTokenRedis)){
            return false;
        }
        if(Objects.equals(accessTokenRedis,accessToken)){
            //一致则保存用户id
            UserIdUtil.set(Long.valueOf(String.valueOf(userId)));
            return true;
        }
        return false;
    }


    /**
     * 校验路径是否需要登录检测
     * @param proceedingJoinPoint
     * @return
     */
    private boolean checkNeedCheckLoginInfo(ProceedingJoinPoint proceedingJoinPoint) {
        Signature signature = proceedingJoinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        return !method.isAnnotationPresent(LoginIgnore.class);
    }


}
